Skip to content

test(runtime): shared ContainerRuntime contract suite across both backends (RIG-2493) - #720

Merged
mattwilkinsonn merged 1 commit into
mainfrom
compass-runner/rig-2493-u5-contract-suite
Aug 30, 2026
Merged

test(runtime): shared ContainerRuntime contract suite across both backends (RIG-2493)#720
mattwilkinsonn merged 1 commit into
mainfrom
compass-runner/rig-2493-u5-contract-suite

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

U5 of the frozen microVM Runner V2b plan (docs/designs/infra/runtime/compass-elastic-session-runtime/microvm-v2b-guest-supervisor-exec.md, §Plan U5) — the V2b acceptance gate. One table-driven contract suite proves MicroVMRuntime and PodmanCLI behave identically through the runtime.ContainerRuntime interface, run against BOTH backends, with the 6 conceded divergences (record 580-593) encoded as a per-backend capability matrix so a divergence that silently widens fails a row.

What

  • contract_suite_test.go (UNTAGGED, package runtime): the shared body. backendCaps capability descriptor (factory + the 6 divergence flags + typed-error closures) and runContractSuite, a thin dispatcher whose rows are one helper each. It references ONLY untagged production symbols plus backendCaps — every KVM/podman-only symbol (microvmtest, podmanUsable, buildImage, *DuplicateNameError) is reached through a caps closure, so the file compiles on every platform. A var _ = runContractSuite roots the graph for the untagged unused lint pass (both real callers are tag-gated).
  • contract_podman_test.go (//go:build podman): TestContractSuite_Podman — skips unless podmanUsable(), builds the agent image once, containers run sleep infinity as uid 1000; all microVM divergence flags OFF so those rows self-skip; the deliberate-kill row asserts the byte-identical *exec.ExitError path (podman byte-path unregressed).
  • contract_microvm_test.go (//go:build microvm && unix): TestContractSuite_MicroVMmicrovmtest.Require(t), sessions with a /workspace share + uid 1000; all 6 divergence flags ON so every divergence row runs and a silent widening fails. Plus TestMicroVMQBudget, informational boot-latency + per-process PSS via t.Logf (record §(g)), NOT a boot gate.
  • Fold, not duplicate: TestMicroVMLifecycleEndToEnd (Create→Start→Exec→Stop→Remove) and TestMicroVMExecStreamingKillSignalsExit (ExecStreaming-kill), previously standalone in microvm_lifecycle_microvm_test.go, are now shared contract rows and were folded out of that file. e2eConfig and TestMicroVMStartFailureLeavesNoState (a microVM-backend-only negative with no podman analog) stay.

Divergence 6 (deliberate-kill error shape)

The shared row body proves the SAME behavioral contract — a deliberate Kill+Wait surfaces as a signalled exit isDeliberateKill accepts — in the two shapes the frozen code matches: microVM's portable *ExitStatusError (Signal != 0) and podman's byte-identical *exec.ExitError (ExitCode() == -1). The error SHAPE is capability-gated (portableKillError) rather than forced to one type, because asserting *ExitStatusError on podman would require a forbidden production change to PodmanCLI and contradict record 590-591. This honors the frozen record.

Verification

gofmt -l clean; go build/go vet clean untagged, under -tags 'microvm unix', and under -tags podman; go test -race -count=1 ./internal/runtime/... ok; golangci-lint run 0 issues (untagged, and under each tag for the touched files). Live: go test -tags podman -run TestContractSuite_Podman PASS (all shared rows; microVM-gated rows correctly absent). Live on KVM: go test -tags 'microvm unix' -run TestContractSuite_MicroVM — all 15 rows PASS (the exec rows exercise the U2 guest PATH resolution and the whole suite the U4 socket-budget fix).

Ledger-impact: none

Spec-impact: none. Refs RIG-2493

Co-authored-by: Matt Wilkinson matt@rigel.build

@linear-code

linear-code Bot commented Aug 28, 2026

Copy link
Copy Markdown

RIG-2493

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-runner-rig-2493-u5-c.compass-eng-docs.pages.dev

Deployed from compass-runner/rig-2493-u5-contract-suite at 738bcb4.

@rigel-mintaka
rigel-mintaka force-pushed the compass-runner/rig-2493-u4-microvm-lifecycle branch from b95a89a to 9f07047 Compare August 28, 2026 11:11
@rigel-mintaka
rigel-mintaka force-pushed the compass-runner/rig-2493-u5-contract-suite branch from 79f96e3 to e664f7e Compare August 28, 2026 11:12
@rigel-mintaka
rigel-mintaka force-pushed the compass-runner/rig-2493-u4-microvm-lifecycle branch from 9f07047 to 1975856 Compare August 29, 2026 19:37
@rigel-mintaka
rigel-mintaka force-pushed the compass-runner/rig-2493-u5-contract-suite branch from e664f7e to 07a129a Compare August 29, 2026 19:39
Base automatically changed from compass-runner/rig-2493-u4-microvm-lifecycle to main August 30, 2026 04:16
…kends (RIG-2493)

U5 of the frozen microVM Runner V2b plan (`docs/designs/infra/runtime/compass-elastic-session-runtime/microvm-v2b-guest-supervisor-exec.md`, §Plan U5) — the V2b acceptance gate. One table-driven contract suite proves `MicroVMRuntime` and `PodmanCLI` behave identically through the `runtime.ContainerRuntime` interface, run against BOTH backends, with the 6 conceded divergences (record 580-593) encoded as a per-backend capability matrix so a divergence that silently *widens* fails a row.

### What

- **`contract_suite_test.go`** (UNTAGGED, package `runtime`): the shared body. `backendCaps` capability descriptor (factory + the 6 divergence flags + typed-error closures) and `runContractSuite`, a thin dispatcher whose rows are one helper each. It references ONLY untagged production symbols plus `backendCaps` — every KVM/podman-only symbol (`microvmtest`, `podmanUsable`, `buildImage`, `*DuplicateNameError`) is reached through a caps closure, so the file compiles on every platform. A `var _ = runContractSuite` roots the graph for the untagged `unused` lint pass (both real callers are tag-gated).
- **`contract_podman_test.go`** (`//go:build podman`): `TestContractSuite_Podman` — skips unless `podmanUsable()`, builds the agent image once, containers run `sleep infinity` as uid 1000; all microVM divergence flags OFF so those rows self-skip; the deliberate-kill row asserts the byte-identical `*exec.ExitError` path (podman byte-path unregressed).
- **`contract_microvm_test.go`** (`//go:build microvm && unix`): `TestContractSuite_MicroVM` — `microvmtest.Require(t)`, sessions with a `/workspace` share + uid 1000; all 6 divergence flags ON so every divergence row runs and a silent widening fails. Plus `TestMicroVMQBudget`, informational boot-latency + per-process PSS via `t.Logf` (record §(g)), NOT a boot gate.
- **Fold, not duplicate**: `TestMicroVMLifecycleEndToEnd` (Create→Start→Exec→Stop→Remove) and `TestMicroVMExecStreamingKillSignalsExit` (ExecStreaming-kill), previously standalone in `microvm_lifecycle_microvm_test.go`, are now shared contract rows and were folded out of that file. `e2eConfig` and `TestMicroVMStartFailureLeavesNoState` (a microVM-backend-only negative with no podman analog) stay.

### Divergence 6 (deliberate-kill error shape)

The shared row body proves the SAME behavioral contract — a deliberate Kill+Wait surfaces as a signalled exit `isDeliberateKill` accepts — in the two shapes the frozen code matches: microVM's portable `*ExitStatusError` (`Signal != 0`) and podman's byte-identical `*exec.ExitError` (`ExitCode() == -1`). The error SHAPE is capability-gated (`portableKillError`) rather than forced to one type, because asserting `*ExitStatusError` on podman would require a forbidden production change to `PodmanCLI` and contradict record 590-591. This honors the frozen record.

### Verification

`gofmt -l` clean; `go build`/`go vet` clean untagged, under `-tags 'microvm unix'`, and under `-tags podman`; `go test -race -count=1 ./internal/runtime/...` ok; `golangci-lint run` 0 issues (untagged, and under each tag for the touched files). Live: `go test -tags podman -run TestContractSuite_Podman` PASS (all shared rows; microVM-gated rows correctly absent). Live on KVM: `go test -tags 'microvm unix' -run TestContractSuite_MicroVM` — all 15 rows PASS (the exec rows exercise the U2 guest PATH resolution and the whole suite the U4 socket-budget fix).

Ledger-impact: none

Spec-impact: none. Refs RIG-2493

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka
rigel-mintaka force-pushed the compass-runner/rig-2493-u5-contract-suite branch from 07a129a to 738bcb4 Compare August 30, 2026 04:19
rigel-mintaka added a commit that referenced this pull request Aug 30, 2026
The reap-wait loop gave the ExecStream cancel→SIGKILL→reap path only 10s
of wall-clock to remove the exec_id from the table. That races HTTP/2
RST_STREAM propagation over the loopback h2c transport plus goroutine
scheduling: the reap fires only once the receive-loop observes the client
cancel as a stream error. On a saturated CI runner (deploy+e2e+microvm+
pgtest+4 moon legs concurrent) that delivery exceeded 10s and the test
false-failed with 'child still present after stream break' (moon (go) leg,
PR #720 CI, test wall 15.01s).

The reap logic is correct and always completes; only the test ceiling was
too tight. Widen to 30s — a genuine never-reap hang still fails at the
ceiling, while extreme load no longer trips a false positive. Local
reproduction of the original: 80/80 PASS (10x plain, 30x -race, 25x
GOMAXPROCS=1, 15x under full 32-core saturation). Test-only, no production
delta. Flaky test = real bug, no retries.

Refs RIG-2980

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@mattwilkinsonn
mattwilkinsonn merged commit ff120d0 into main Aug 30, 2026
24 of 26 checks passed
@mattwilkinsonn
mattwilkinsonn deleted the compass-runner/rig-2493-u5-contract-suite branch August 30, 2026 17:22
mattwilkinsonn added a commit that referenced this pull request Aug 30, 2026
#732)

The reap-wait loop gave the ExecStream cancel→SIGKILL→reap path only 10s
of wall-clock to remove the exec_id from the table. That races HTTP/2
RST_STREAM propagation over the loopback h2c transport plus goroutine
scheduling: the reap fires only once the receive-loop observes the client
cancel as a stream error. On a saturated CI runner (deploy+e2e+microvm+
pgtest+4 moon legs concurrent) that delivery exceeded 10s and the test
false-failed with 'child still present after stream break' (moon (go) leg,
PR #720 CI, test wall 15.01s).

The reap logic is correct and always completes; only the test ceiling was
too tight. Widen to 30s — a genuine never-reap hang still fails at the
ceiling, while extreme load no longer trips a false positive. Local
reproduction of the original: 80/80 PASS (10x plain, 30x -race, 25x
GOMAXPROCS=1, 15x under full 32-core saturation). Test-only, no production
delta. Flaky test = real bug, no retries.

Refs RIG-2980

Co-authored-by: Matt Wilkinson <matt@rigel.build>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants